CSS 개발자를 위한 GPU 2D 에디터 만들기
Guide
CSS Matrix
Guide
CSS Matrix
  • Part 0. CSS에서 GPU로 사고 전환

    • CSS 박스가 사라지면 무엇이 남는가
    • Canvas는 DOM이 아니라 픽셀 버퍼다
    • CSS pixel, device pixel, backing store
    • scene model과 renderer 분리하기
    • retained mode vs immediate mode
    • DOM event target 없이 hit testing 하기
  • Part 1. Canvas editor의 기본 뼈대

    • render loop와 frame lifecycle
    • viewport와 camera
    • screen/world/local 좌표계 복습
    • cursor anchored zoom
    • grid와 ruler를 canvas에 그리기
    • overlay layer와 control layer 분리
  • Part 2. WebGL 2D renderer

    • WebGL context와 첫 triangle
    • clip space와 화면 좌표
    • shader를 CSS transform 관점으로 읽기
    • rectangle을 두 triangle로 그리기
    • matrix uniform으로 camera 적용하기
    • 색상, alpha, blending
    • 여러 rectangle을 buffer에 담기
    • batching과 draw call 줄이기
    • instanced rectangle renderer
    • line/outline renderer
  • Part 3. Editor tool math

    • pointer 좌표를 world 좌표로 바꾸기
    • CPU hit testing
    • selection bounds
    • hover outline
    • move tool
    • resize handles
    • rotation handle
    • marquee selection
    • snapping과 smart guides
    • group transform
  • Part 4. Figma-like scene graph

    • node tree 설계
    • transform inheritance
    • layer order와 z sorting
    • frame과 clipping
    • fill, stroke, effect 모델
    • command model과 undo/redo
    • JSON export/import
    • renderer-independent editor core
  • Part 5. Image, text, vector

    • texture로 image node 그리기
    • texture atlas 기본
    • text rendering 전략
    • SVG/text를 GPU editor에서 다루는 경계
    • vector path는 어디까지 직접 구현할 것인가
  • Part 6. WebGPU로 옮기기

    • WebGPU adapter/device/context
    • WGSL과 render pipeline
    • WebGL renderer를 WebGPU renderer로 바꾸기
    • uniform buffer와 bind group
    • WebGPU instancing
    • WebGL/WebGPU fallback 전략
  • Part 7. Capstone

    • editor shell 만들기
    • toolbar / layer panel / inspector 연결
    • mini Figma-like editor 완성
    • 성능 점검과 디버깅
    • 배포와 브라우저 호환성 체크
  • Part 8. Three.js로 WebGL 개발하기

    • Three.js를 WebGL renderer로 쓰는 기준
    • Scene, Camera, Renderer, render loop
    • OrthographicCamera로 2D editor 좌표계 만들기
    • BufferGeometry, Material, ShaderMaterial
    • Raycaster와 editor picking
    • Three.js renderer를 editor core 뒤에 붙이기
    • Three.js 프로젝트 세팅과 renderer lifecycle
    • Object3D transform과 editor scene graph 매핑
    • InstancedMesh로 많은 rectangle 그리기
    • Texture, CanvasTexture, Sprite로 이미지/텍스트 다루기
    • Three.js에서 outline, selection, overlay 만들기
    • dispose, cache, renderer.info로 성능 관리하기
    • WebGPURenderer와 TSL로 넘어가는 길
    • RenderTarget을 이용한 picking buffer
    • Three.js를 쓰면 안 좋은 경우
  • Appendix A. GPU editor debugging

    • WebGL/WebGPU 디버그 overlay 만들기
    • 좌표계, matrix, bounds readout 설계
    • frame time, draw call, buffer upload 측정하기
    • Spector.js / Chrome DevTools로 WebGL 프레임 보기
  • Appendix B. Browser and GPU compatibility

    • WebGL/WebGPU feature detection 체크리스트
    • DPR, resize, context lost 처리
    • Safari/Chrome/Firefox 차이와 fallback 정책
    • GPU memory와 texture size 제한
  • Appendix C. Asset pipeline

    • 이미지 로딩, ImageBitmap, texture upload
    • SVG를 texture로 쓸지 vector로 유지할지
    • 폰트 로딩과 text metrics
    • export용 PNG/SVG/JSON 생성 전략
  • Appendix D. Interaction polish and motion

    • inertial pan과 smooth zoom
    • snapping feedback animation
    • selection/hover transition
    • timeline 없이 필요한 최소 모션 수학
  • Appendix E. Production architecture

    • renderer worker / OffscreenCanvas를 고려하는 기준
    • document model versioning과 migration
    • plugin architecture와 command API
    • test 가능한 renderer abstraction 만들기
  • Appendix F. 2D renderer engine patterns

    • renderable type 선택: shape, sprite, mesh
    • static subtree를 texture cache로 굽기
    • render layer와 render group 설계
    • viewport culling과 spatial index
    • clipping 구현: scissor, stencil, mask texture
    • filters와 blend modes가 batch를 깨는 이유
    • interactivity budget: pickable, hitArea, skip children
    • texture GC와 idle resource eviction
    • dynamic text update 비용과 bitmap/glyph 전략
    • Canvas/WebGL editor의 accessibility layer
  • Appendix G. Rendering editor production gaps

    • render invalidation과 dirty flag
    • color space, premultiplied alpha, export 색상
    • stroke join/cap/dash/fill rule
    • editable text: DOM overlay, IME, caret, metrics
    • tool state machine과 pointer capture
    • pixel test와 renderer regression test

retained mode vs immediate mode

CSS와 DOM으로 UI를 만들 때 우리는 retained mode 환경에 살고 있습니다. 요소를 한 번 만들어두면 브라우저가 그 구조를 계속 기억합니다.

const node = document.createElement("div");
node.style.transform = "translate(120px, 80px)";
document.body.append(node);

이 코드를 실행하고 나면 DOM 안에 요소가 남습니다. 나중에 style을 바꾸면 브라우저가 이전 상태와 현재 상태를 비교해서 필요한 렌더링을 다시 합니다.

Canvas와 WebGL/WebGPU는 보통 다르게 생각합니다. 매 프레임 우리가 다시 그립니다.

clear
draw rect A
draw rect B
draw selection outline
present

이 차이가 편집기 구조를 크게 바꿉니다.

retained mode는 상태를 시스템이 기억한다

DOM은 retained mode API입니다. 우리가 요소와 속성을 등록하면 브라우저가 그 상태를 유지합니다.

DOM tree
  -> style
  -> layout
  -> paint
  -> composite

그래서 CSS 기반 에디터에서는 화면에 있는 것과 모델이 가까워 보입니다. 실제로는 DOM도 브라우저 내부 렌더링 모델로 변환되지만, 개발자 입장에서는 요소가 살아 있는 객체처럼 보입니다.

이 방식의 장점은 큽니다.

  • DevTools에서 요소를 바로 볼 수 있습니다.
  • event target을 브라우저가 찾아줍니다.
  • style 변경이 선언적입니다.
  • accessibility와 text selection 같은 브라우저 기능을 활용할 수 있습니다.

하지만 수천 개의 도형, 복잡한 overlay, 빠른 pan/zoom, 직접 제어하는 픽셀 효과가 필요하면 이 장점이 부담으로 바뀔 수 있습니다.

immediate mode는 매 프레임 명령을 보낸다

Canvas 2D를 보면 immediate mode 감각이 잘 드러납니다.

ctx.clearRect(0, 0, width, height);
ctx.fillRect(120, 80, 200, 120);
ctx.strokeRect(120, 80, 200, 120);

이 명령은 실행되는 순간 픽셀을 바꿉니다. fillRect가 끝난 뒤 Canvas가 “사각형 객체”를 기억하지 않습니다. 다음 프레임에 위치가 바뀌면 다시 지우고 다시 그립니다.

WebGL/WebGPU도 API 모양은 다르지만 편집기 입장에서는 같은 방향입니다. buffer와 pipeline 같은 GPU 리소스는 유지하지만, 화면에 어떤 순서로 무엇을 그릴지는 render pass에서 다시 구성합니다.

frame N:
  upload changed data
  encode draw commands
  submit

frame N + 1:
  upload changed data
  encode draw commands
  submit

편집기 model은 retained, renderer는 immediate

여기서 중요한 결론이 나옵니다. GPU 에디터 전체가 immediate mode가 되는 것은 아닙니다.

편집기 문서 자체는 retained model이어야 합니다.

const scene = {
  nodes: [
    { id: "a", type: "rect", x: 120, y: 80 },
    { id: "b", type: "rect", x: 280, y: 140 }
  ],
  selection: ["a"]
};

사용자가 만든 도형, 선택 상태, 레이어 순서, undo history는 계속 남아야 합니다. 사라지는 것은 DOM 요소이지 문서 모델이 아닙니다.

그래서 우리는 이렇게 나눕니다.

editor core: retained model
renderer: immediate draw commands

이 구분이 있으면 CSS 경험을 버릴 필요가 없습니다. CSS에서 DOM이 해주던 retained 상태 관리를 이제 editor core가 맡고, 렌더러는 그 상태를 빠르게 그리는 일에 집중합니다.

잘못된 경계의 냄새

초반 구현에서 자주 나오는 실수는 renderer cache를 문서 상태처럼 쓰는 것입니다.

GPU buffer에 있으니까 이게 node 위치다
selection color를 shader uniform에 넣었으니 이게 선택 상태다

이렇게 되면 undo/redo, export/import, WebGL/WebGPU 교체가 어려워집니다. GPU buffer는 캐시입니다. 캐시는 버리고 다시 만들 수 있어야 합니다. 문서 상태는 scene model에 있어야 합니다.

좋은 기준은 이 질문입니다.

브라우저를 새로고침한 뒤 JSON만 읽어서 복원해야 하는가?

그렇다면 editor model에 들어가야 합니다. 특정 renderer가 빠르게 그리기 위해 만든 값이라면 renderer cache에 둡니다.

오늘의 핵심

CSS에서 GPU canvas로 넘어갈 때 retained mode를 버리는 것이 아닙니다. retained model을 브라우저 DOM에서 우리 editor core로 옮기는 것입니다.

CSS/DOM editor:
  browser keeps DOM state
  browser renders changes

GPU editor:
  editor core keeps scene state
  renderer draws current frame

이 차이를 이해하면 다음 레슨의 hit testing도 자연스럽게 이어집니다. DOM event target이 없으니, retained scene model을 기준으로 직접 맞은 도형을 찾아야 합니다.

최근 수정: 26. 5. 16. PM 12:53
Contributors: jinho.park.s3
Prev
scene model과 renderer 분리하기
Next
DOM event target 없이 hit testing 하기