diff --git a/README.md b/README.md index 5c638f6..c3851a7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ See issue [#26]. For an experimental Live Update feature compile the application $ nimble build -d:live ``` +For a faster Live Update feature based on MIT-SHM X11 extension use `-d:mitshm`: + +```console +$ nimble build -d:live -d:mitshm +``` + The feature is really unstable and experimental, so use it at your own risk. ## NixOS Overlay diff --git a/src/boomer.nim b/src/boomer.nim index 142c1e0..a2c68e6 100644 --- a/src/boomer.nim +++ b/src/boomer.nim @@ -1,7 +1,7 @@ import os import navigation -import image +import screenshot import config import x11/xlib, x11/x, x11/xutil, x11/keysym, x11/xrandr, x11/xshm @@ -11,11 +11,6 @@ import syscall type Shader = tuple[path, content: string] -const - IPC_PRIVATE = 0 - IPC_CREAT = 512 - IPC_RMID = 0 - proc readShader(file: string): Shader = when nimvm: result.path = file @@ -97,7 +92,7 @@ proc update(flashlight: var Flashlight, dt: float32) = else: flashlight.shadow = max(flashlight.shadow - 6.0 * dt, 0.0) -proc draw(screenshot: Image, camera: Camera, shader, vao, texture: GLuint, +proc draw(screenshot: PXImage, camera: Camera, shader, vao, texture: GLuint, windowSize: Vec2f, mouse: Mouse, flashlight: Flashlight) = glClearColor(0.1, 0.1, 0.1, 1.0) glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) @@ -181,27 +176,6 @@ proc main() = DefaultRootWindow(display), addr attributes) - var shminfo: TXShmSegmentInfo - var screenshot = XShmCreateImage( - display, vi.visual, 24.cuint, ZPixmap, nil, - addr shminfo, - attributes.width.cuint, - attributes.height.cuint) - - shminfo.shmid = syscall(SHMGET, - IPC_PRIVATE, - screenshot.bytes_per_line * screenshot.height, - IPC_CREAT or 0o777).cint - shminfo.shmaddr = cast[cstring](syscall(SHMAT, shminfo.shmid, 0, 0)) - screenshot.data = shminfo.shmaddr - shminfo.readOnly = 0 - - let err = XShmAttach(display, addr shminfo) - echo "Status of XShmAttach call = ", err - discard XSync(display, 0) - - discard XShmGetImage(display, DefaultRootWindow(display), screenshot, 0.cint, 0.cint, AllPlanes); - var win = XCreateWindow( display, root, 0, 0, attributes.width.cuint, attributes.height.cuint, 0, @@ -231,8 +205,15 @@ proc main() = var shaderProgram = newShaderProgram(vertexShader, fragmentShader) - let w = screenshot.width.float32 - let h = screenshot.height.float32 + var screenshot = + when defined(mitshm): + newMitshmScreenshot(display) + else: + newDefaultScreenshot(display) + defer: screenshot.destroy(display) + + let w = screenshot.image.width.float32 + let h = screenshot.image.height.float32 var vao, vbo, ebo: GLuint vertices = [ @@ -279,13 +260,13 @@ proc main() = glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB.GLint, - screenshot.width, - screenshot.height, + screenshot.image.width, + screenshot.image.height, 0, # TODO(#13): the texture format is hardcoded GL_BGRA, GL_UNSIGNED_BYTE, - screenshot.data) + screenshot.image.data) glGenerateMipmap(GL_TEXTURE_2D) glUniform1i(glGetUniformLocation(shaderProgram, "tex".cstring), 0) @@ -403,52 +384,29 @@ proc main() = else: discard - camera.update(config, dt, mouse, screenshot, + camera.update(config, dt, mouse, screenshot.image, vec2(wa.width.float32, wa.height.float32)) flashlight.update(dt) - screenshot.draw(camera, shaderProgram, vao, texture, - vec2(wa.width.float32, wa.height.float32), - mouse, flashlight) + screenshot.image.draw(camera, shaderProgram, vao, texture, + vec2(wa.width.float32, wa.height.float32), + mouse, flashlight) glXSwapBuffers(display, win) glFinish() - discard XShmGetImage(display, DefaultRootWindow(display), screenshot, 0.cint, 0.cint, AllPlanes); - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGB.GLint, - screenshot.width, - screenshot.height, - 0, - # TODO(#13): the texture format is hardcoded - GL_BGRA, - GL_UNSIGNED_BYTE, - screenshot.data) - when defined(live): - screenshot = XGetSubImage(display, root, - 0, 0, - screenshot.width.cuint, - screenshot.height.cuint, - AllPlanes, - ZPixmap, - screenshot, - 0, 0) + screenshot.refresh(display) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB.GLint, - screenshot.width, - screenshot.height, + screenshot.image.width, + screenshot.image.height, 0, # TODO(#13): the texture format is hardcoded GL_BGRA, GL_UNSIGNED_BYTE, - screenshot.data) - - discard XShmDetach(display, addr shminfo) - discard XDestroyImage(screenshot) - discard syscall(SHMDT, shminfo.shmaddr) - discard syscall(SHMCTL, shminfo.shmid, IPC_RMID, 0) + screenshot.image.data) + discard XSync(display, 0) main() diff --git a/src/image.nim b/src/image.nim deleted file mode 100644 index 5ae5c57..0000000 --- a/src/image.nim +++ /dev/null @@ -1,29 +0,0 @@ -import x11/xlib, x11/x - -type Image* = PXImage - -proc saveToPPM*(image: Image, filePath: string) = - var f = open(filePath, fmWrite) - defer: f.close - writeLine(f, "P6") - writeLine(f, image.width, " ", image.height) - writeLine(f, 255) - for i in 0..<(image.width * image.height): - f.write(image.data[i * 4 + 2]) - f.write(image.data[i * 4 + 1]) - f.write(image.data[i * 4 + 0]) - -# NOTE: it's not possible to deallocate the returned Image because the -# reference to XImage is lost. -proc takeScreenshot*(display: PDisplay, root: TWindow): Image = - var attributes: TXWindowAttributes - discard XGetWindowAttributes(display, root, addr attributes) - - result = XGetImage(display, root, - 0, 0, - attributes.width.cuint, - attributes.height.cuint, - AllPlanes, - ZPixmap) - if result == nil: - quit "Could not get a screenshot" diff --git a/src/navigation.nim b/src/navigation.nim index 646e597..be3a487 100644 --- a/src/navigation.nim +++ b/src/navigation.nim @@ -1,6 +1,7 @@ +import x11/xlib + import config import la -import image const VELOCITY_THRESHOLD = 15.0 @@ -19,8 +20,7 @@ type Camera* = object proc world*(camera: Camera, v: Vec2f): Vec2f = v / camera.scale -proc update*(camera: var Camera, config: Config, dt: float, mouse: Mouse, image: Image, - windowSize: Vec2f) = +proc update*(camera: var Camera, config: Config, dt: float, mouse: Mouse, image: PXImage, windowSize: Vec2f) = if abs(camera.deltaScale) > 0.5: let p0 = (camera.scalePivot - (windowSize * 0.5)) / camera.scale camera.scale = max(camera.scale + camera.delta_scale * dt, 0.01) diff --git a/src/screenshot.nim b/src/screenshot.nim new file mode 100644 index 0000000..3774394 --- /dev/null +++ b/src/screenshot.nim @@ -0,0 +1,108 @@ +import x11/xlib, x11/x, x11/xutil, x11/xshm +import syscall + +const + IPC_PRIVATE = 0 + IPC_CREAT = 512 + IPC_RMID = 0 + +type ScreenshotBackend* = enum + DEFAULT, # XGetImage + MITSHM # XShmGetImage + +type Screenshot* = object + backend*: ScreenshotBackend + image*: PXImage + shminfo*: PXShmSegmentInfo + +proc newDefaultScreenshot*(display: PDisplay): Screenshot = + result.backend = DEFAULT + + var root = DefaultRootWindow(display) + var attributes: TXWindowAttributes + discard XGetWindowAttributes(display, root, addr attributes) + result.image = XGetImage(display, root, + 0, 0, + attributes.width.cuint, + attributes.height.cuint, + AllPlanes, + ZPixmap) + +proc newMitshmScreenshot*(display: PDisplay): Screenshot = + result.backend = MITSHM + result.shminfo = cast[PXShmSegmentInfo](allocShared(sizeof(TXShmSegmentInfo))) + + var root = DefaultRootWindow(display) + var attributes: TXWindowAttributes + discard XGetWindowAttributes(display, root, addr attributes) + + let screen = DefaultScreen(display) + result.image = XShmCreateImage( + display, + DefaultVisual(display, screen), + DefaultDepthOfScreen(ScreenOfDisplay(display, screen)).cuint, + ZPixmap, + nil, + result.shminfo, + attributes.width.cuint, + attributes.height.cuint) + + result.shminfo.shmid = syscall( + SHMGET, + IPC_PRIVATE, + result.image.bytes_per_line * result.image.height, + IPC_CREAT or 0o777).cint + + result.shminfo.shmaddr = cast[cstring](syscall( + SHMAT, + result.shminfo.shmid, + 0, 0)) + result.image.data = result.shminfo.shmaddr + result.shminfo.readOnly = 0 + + discard XShmAttach(display, result.shminfo) + discard XShmGetImage(display, root, result.image, 0.cint, 0.cint, AllPlanes) + +proc refresh*(screenshot: var Screenshot, display: PDisplay) = + var root = DefaultRootWindow(display) + + case screenshot.backend + of DEFAULT: + screenshot.image = + XGetSubImage(display, root, + 0, 0, + screenshot.image.width.cuint, + screenshot.image.height.cuint, + AllPlanes, + ZPixmap, + screenshot.image, + 0, 0) + of MITSHM: + discard XShmGetImage( + display, + root, screenshot.image, + 0.cint, 0.cint, + AllPlanes) + +proc destroy*(screenshot: var Screenshot, display: PDisplay) = + case screenshot.backend + of DEFAULT: + discard XDestroyImage(screenshot.image) + of MITSHM: + discard XSync(display, 0) + discard XShmDetach(display, screenshot.shminfo) + discard XDestroyImage(screenshot.image) + discard syscall(SHMDT, screenshot.shminfo.shmaddr) + discard syscall(SHMCTL, screenshot.shminfo.shmid, IPC_RMID, 0) + deallocShared(screenshot.shminfo) + +proc saveToPPM*(image: PXImage, filePath: string) = + var f = open(filePath, fmWrite) + defer: f.close + writeLine(f, "P6") + writeLine(f, image.width, " ", image.height) + writeLine(f, 255) + for i in 0..<(image.width * image.height): + f.write(image.data[i * 4 + 2]) + f.write(image.data[i * 4 + 1]) + f.write(image.data[i * 4 + 0])