저는 소프트웨어 렌더링이 좋습니다

I Love Software Rendering

jamesfmackenzie.com ▲ 282 댓글 188 speckx

요약

고전 도스 게임은 3D 카드보다 소프트웨어 렌더링으로 할 때 더 보기 좋고, 지금의 빠른 PC라면 고해상도로도 돌릴 수 있습니다.

레트로 PC를 즐기는 제임스 매켄지가 자기 블로그에 쓴 글입니다. 1990년대 게이머는 CPU가 모든 픽셀을 직접 그리는 저해상도 소프트웨어 렌더링과 3D 카드 가속 가운데 하나를 골라야 했습니다. 그는 지금도 대부분 소프트웨어 렌더러를 고른다고 밝혔습니다.

왜 중요한가

  • 고전 게임 특유의 느낌이 낮은 해상도가 아니라 렌더링 방식에서 나온다는 관점입니다.
  • 당시 하드웨어로는 버거웠던 VESA(그래픽 카드 고해상도 표준) 모드를 이제야 제대로 즐길 수 있다는 뜻입니다.

핵심 내용

  • 소프트웨어 렌더러는 텍스처를 픽셀 그대로 찍어 선명하지만, 초기 3D 카드는 쌍선형 필터링(이웃 픽셀을 섞는 기법)으로 흐리게 만들었다고 봅니다.
  • 256색 팔레트와 조명용 색상표 때문에 그림자에 생기는 색 띠도 거친 분위기를 만든다고 했습니다.
  • 그가 꼽은 최고의 경험은 델 OptiPlex 380에서 Quake를 800×600, 60fps로 돌리는 것입니다.
  • 해상도가 오를수록 CPU 부담도 커져서, 3.3GHz 쿼드코어 Xeon도 Shadow Warrior를 1600×1200으로는 다 감당하지 못했다고 합니다.
  • 다만 Tomb Raider는 가속판의 물속 장면이 더 낫다며 소프트웨어가 늘 정답은 아니라고 인정했습니다.

HN 반응

  • 여러 이용자는 글쓴이가 좋아하는 것이 텍스처 필터링을 끈 화면일 뿐이고, GPU로도 똑같이 낼 수 있다고 지적했습니다.
  • 반면 소프트웨어 렌더링 시절에는 게임마다 픽셀을 그리는 방식이 달라서, 필터링만 끈 GPU 렌더링으로는 그 고유한 느낌이 나지 않는다는 반론도 나왔습니다.

댓글

30개 표시 · 전체 188개
  1. Waterluvian HN

    아, 소프트웨어 렌더링 시절이죠. 사운드 카드 하나 꽂았더니 10fps가 올랐던 그때요.

  2. fwsgonzo HN

    더 자세히 듣고 싶네요.

  3. hypercube33 HN

    초창기 멀티미디어 PC에서는 CPU가 사운드, 네트워킹, 하드 드라이브 제어 등을 전부 맡았습니다. 비용 절감은 거의 다 보조 부품에 최소한의 하드웨어만 넣고 나머지를 전부 드라이버로 처리하는 데서 나왔습니다.

    흔한 “요령”은 이런 일을 처리할 자체 프로세서를 갖춘 카드를 사는 것이었습니다. 사운드 블래스터(SoundBlaster), 쓰리콤 네트워크 카드, 자체 프로세서(심지어 자체 OS까지) 가 달린 IDE 드라이브 컨트롤러 같은 것들이죠. CPU가 진짜 할 일, 즉 게임에 전념할 수 있었으니까요. 심지어 외장이나 그 밖의

    CPU가 병목이긴 했지만, 대부분의 사람은 일상 업무(사무 작업, 웹 서핑) 정도로는 싱글 코어 머신에 부담을 주지 않아서 문제가 없었습니다. 하지만 사운드, 3D, 네트워킹, 디스크 접근이 한꺼번에 필요한 게임을 돌리는 순간, 그 모든 걸 한 곳에서 처리하느라 클럭 사이클을 빼앗기기 시작했습니다.

  4. KellyCriterion HN

    SOUNDBLASTER 2.0 !!!!!!

    16(!!!)비트 ISA 슬롯 카드였고, PC 오디오 경험을 완전히 뒤바꿔 놓았죠.

    지금도 시장에 나와 있나요, 아니면 점점 늘어난 온보드 칩에 밀려 망했나요?

    그리고 Gravis Ultrasound도 있었어요. 샘플을 사운드 보드의 RAM에 올릴 수 있었는데, 당시에는 그게 초강력 울트라 메가 기술 같았죠 :-) 제 기억으로 최고 사양은 사운드 보드에 달린 무려 망할 16MB짜리 온보드 RAM이었어요.

    :-D

  5. vikingerik HN

    Sound Blaster 2.0은 16비트가 아니었습니다. 1.0보다 샘플링 레이트는 높았지만, 8비트 ISA 슬롯에서 여전히 8비트 오디오를 처리했습니다. Sound Blaster 16은 1~2년 뒤에 나온 별개의 제품이었고요.

  6. dingaling911 HN
        MY NAME IS DOCTOR SBAITSO.
    
    I AM HERE TO HELP YOU.
        SAY WHATEVER IS IN YOUR MIND FREELY,
        OUR CONVERSATION WILL BE KEPT IN STRICT CONFIDENCE.
        MEMORY CONTENTS WILL BE WIPED OFF AFTER YOU LEAVE,
    
    SO, TELL ME ABOUT YOUR PROBLEMS.
    
  7. hallway_monitor HN

    사운드 카드를 달면 사운드 처리가 CPU에서 빠져서, CPU가 프레임을 그릴 사이클이 더 늘어서 그런 것 같아요! 아마 25MHz쯤 하던 시절 얘기겠죠.

  8. BugsJustFindMe HN

    이 글 제목은 사실 “나는 픽셀 깨진 텍스처가 좋다”여야 해요.

    “3D 카드는 바이리니어 필터링으로 텍스처를 부드럽게 만들었다”

    그냥 그거 안 켜면 되잖아요.

  9. bartread HN

    비디오 카드에서 그 개선 기능들만 꺼 두고 돌리면 CPU로 전부 처리하는 것보다 전력 효율도 훨씬 좋을 것 같습니다. 그러면 (a) 노트북 배터리가 한 번 충전으로 더 오래 가고, (b) 비디오 하드웨어가 대신 일해 주니 해상도를 높이려고 굳이 강력한 CPU가 필요하지 않을 가능성도 열립니다.

    프로그래밍 관점에서 CPU가 모든 일을 하길 바라는 마음은 충분히 이해합니다. 모든 걸 직접 통제하고 싶다면 그편이 더 쉽고, 특히 게임이 싱글 스레드라면 더 그렇죠.

  10. delta_p_delta_x HN

    다른 곳에서도 말했지만, 요즘은 HDMI/DP/eDP 케이블로 나오는 화면은 전부 GPU에서 나옵니다. 최신 OS에서는 ‘소프트웨어 렌더링’이 더는 불가능합니다. 직접 프로그래밍하기는 더 어려워도 그만큼 훨씬 빠르고 효율적이니까요. IPMI BMC에도 작은 GPU가 들어 있어서 화면을 처리합니다.

  11. notabotiswear HN

    왜 “불가능”하다는 거죠? 렌더링된 프레임이 디스플레이로 보내지기 전에 그래픽 메모리에 있어야 한다는 뜻이라면, 소프트웨어 렌더링이 쓰이던 시절에도 마찬가지였다고 생각해요.

    CPU가 메인 메모리에서 프레임 전체를 렌더링한다는 뜻이라면, 그걸 불가능하게 만드는 건 아무것도 없어요. 주요 3D 렌더링 소프트웨어는 지금도 대부분 CPU 렌더러를 옵션으로 제공하잖아요. 느릴 뿐이지 할 수는 있어요.

    P.S: 여기서 오프라인 렌더러는 극단적인 예로 든 거예요. 게임 렌더러는, 특히 Quake 같은 장면이라면 훨씬 단순하고 저렴할 게 분명하니까요.

  12. delta_p_delta_x HN

    제가 말한 ‘불가능’은 아주 엄밀한 의미입니다. 화면에 보이는 면을 나타내는, HDMI나 (e)DP가 제공하는 프레임버퍼에 전체 화면 프레임을 직접 blit하는 걸 말합니다. 게임이 ‘테두리 없는 전체 화면’이 아니라 제대로 된 전체 화면일 때는 윈도에서 아직 어느 정도 가능하지만, 그래도 OS는 뒤에서 합성 작업을 하고 있고 GPU도 어느 정도는 관여합니다.

    물론 오프라인 렌더링은 다른 얘기입니다.

  13. amiga386 HN

    그럴 수 있는 경로 자체가 없습니다. GPU마다 다르고, 제가 보기엔 드라이버를 직접 짜더라도 영상 신호에 쓰이는 최종 프레임버퍼에는 CPU 접근을 아예 허용하지 않을 겁니다.

    윈도 합성에 관해서는, 그래픽 API로 독점 전체 화면(exclusive fullscreen)을 얻으면 됩니다. Vulkan에 이런 예제가 있습니다.

    https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/full_screen_exclusive

  14. notabotiswear HN

    여기서 의견이 갈리는 건 ‘렌더링’의 범위 때문인 것 같네요.

  15. consp HN

    VGA 카드에 비어 있던 메모리 슬롯을 채워서 256색으로 1024x786을 쓰던 시절이 있었죠. 512k 비디오 메모리로 그 색 깊이에서 쓸 수 있는 최대는 800x600이었고, 메모리를 512k에서 1024k로 두 배 늘리면 800x600에서 16비트 색을 쓸 수 있었어요. 그때야말로 확실히 소프트웨어 렌더링 시절이었어요. RAMDAC마다 자기 메모리 풀이 필요했으니까요.

  16. shadowgovt HN

    또 요즘은 운영체제 자체가 창 합성 같은 작업에 가속 그래픽 아키텍처를 쓰는데, 그쪽이 더 빠르다는 것도 밝혀졌기 때문이에요. 그리고 창이 흔들리거나 일그러지는 귀여운 시각 효과도, 창이 실제로 3D 렌더링되는 그래픽 객체라면 훨씬 쉽게 만들 수 있고요.

    요즘은 기본 데스크톱 환경조차 3D 그래픽 애플리케이션이에요.

  17. delta_p_delta_x HN

    맞아요. 몇 달 전에 코드 에디터에 왜 그래픽 가속이 필요한지 설명하는 댓글1을 쓴 적이 있는데, 그 내용과 이어지는 얘기예요.

  18. sorenjan HN

    픽셀로 된 2D 그래픽을 요즘 “8비트”라고 부르는 것과 똑같아요.

  19. the__alchemist HN

    동의해요. 개념이 뒤섞인 느낌이에요. CPU 렌더링이냐 GPU 렌더링이냐가 겉모습을 직접 바꾸는 건 아니거든요. 글은 뒤에서도 OS 비교, 그리고 Steam과 Ion Fury 등에 관한 대목에서 또 개념을 뒤섞어요.

  20. buffportion HN

    텍스처 매핑의 차이 말고도, GPU가 삼각형을 렌더링하는 방식은 옛날에 흔히 쓰던 기법과 꽤 다릅니다. 무게중심 좌표를 계산해서 모든 픽셀이 삼각형 안에 있는지 확인하는 사람은 없었습니다. 싱글 스레드로 하기엔 너무 느리니까요. 낮은 해상도에서는 시각적 결과가 아주 다르게 나오는데, 안티앨리어싱이 없으면 GPU는 픽셀 중심을 덮지 않는 삼각형을 렌더링하지 않기 때문입니다.

  21. delta_p_delta_x HN

    이건 소프트웨어 렌더링이 아니라 공간 안티앨리어싱을 끄는 겁니다. 가장 최신 게임에서도 얼마든지 할 수 있어요. 소프트웨어 렌더링이 사라진 건, 게임을 하는 동안 CPU는 4x4 행렬 곱셈만 하고 있을 수 없고 그 일이 GPU의 셰이더 코어로 넘어갔기 때문입니다. 글쓴이가 말하는 그림자·텍스처 LUT를 포함한 룩앤필도 테이블을 읽기 전용 상수 스토리지 버퍼로 셰이더에 넘겨주면 GPU에서 똑같이 구현할 수 있고, 소프트웨어 렌더링에만 해당하는 게 아닙니다. 또 LLVMpipe/Lavapipe, OpenSWR, WARP 같은 현대의 많은 소프트웨어 렌더러는 셰이더 컴파일러까지 갖춘, 사실상 완전한 D3D/OpenGL/Vulkan 장치라는 점도 짚어 둘 만합니다.

    또 현대의 공간 안티앨리어싱은 한 픽셀(또는 이웃 픽셀까지) 안에서 멀티샘플링한 뒤 평균을 내는 방식이지, 샘플 하나씩인 픽셀들 사이를 필터링하는 방식이 아닙니다. 후자는 품질을 크게 떨어뜨리고요. DLSS, XeSS, FSR을 비롯한 시간적(temporal) AA와 머신러닝 AA 기법, 그리고 많은 게임의 TAA 구현들은 말할 것도 없습니다.

  22. eska HN

    저자가 내린 소프트웨어 렌더링의 정의는 이렇습니다.

      소프트웨어 렌더링 게임에서는 CPU가 모든 픽셀을 그린다. 3D 카드는 관여하지 않는다. 게임이 자체 코드로 각 프레임을 계산해서 비디오 메모리에 곧바로 쓴다
    

    이게 표준적인 정의라고 생각합니다. 이 스타일을 그래픽 하드웨어로도 구현할 수 있다는 뜻이라면 그건 다른 논쟁이고, 그래서 첫 문장이 논란이 된 겁니다.

  23. delta_p_delta_x HN

    이 스타일을 그래픽 하드웨어로도 구현할 수 있다는 뜻이라면

    말꼬리 잡기를 막으려면 제 첫 문장을 더 구체적으로 썼어야 했네요.

    저자는 소프트웨어 렌더링을 사랑한다고 했지만, 소프트웨어 렌더링의 장점이라며 든 예는 전부 GPU에서도 구현할 수 있습니다. 대신 소프트웨어 렌더러로는 래스터화 파이프라인의 내부 동작을 쉽게 디버깅하고 들여다볼 수 있다는 점을 강조했다면 훨씬 더 적절한 주장이었을 겁니다.

  24. swinglock HN

    저자는 그 룩이 좋다고 한 거예요. 주관적인 취향이라 논거가 필요 없죠. 당신이 읽고 싶었던 건 다른, 기술적인 글이고요.

  25. ryandrake HN

    저자가 좋아하는 건 소프트웨어 렌더링이 아니라 최근접 이웃(nearest neighbor) 텍스처 샘플링의 룩이에요.

    하드웨어 렌더링으로도 최근접 이웃 방식을 쓸 수 있고, 소프트웨어 렌더링으로도 바이리니어를 쓸 수 있잖아요. 렌더링 방식과 텍스처 샘플링은 완전히 별개예요.

  26. agumonkey HN

    기술적으로는 맞는 말입니다. GPU는 어떤 면에서 ALU와 FPU 덩어리일 뿐이고, 우리는 연산을 그 거대한 배열에 넘겨서 CPU가 다른 일을 하게 하는 거니까요. 그 GPU 코어에서 뭘 돌릴지는 마음대로고요.

    하지만 역사적으로 소프트웨어 렌더링은 CPU가 쓸 만한 FPS로 처리할 수 있는, 가속 없는 렌더링 패스를 뜻했습니다.

    ps: 초기 x86은 CPU 안에 FPU가 없었으니 FPU를 쓰는 것도 소프트웨어 렌더링이 아니라고 할 사람도 있겠네요.

  27. torginus HN

    제 생각엔 그 이상의 것이 있습니다. Quake의 소프트웨어 렌더러는 CPU에서 코드를 돌려 각 픽셀의 색을 정하고, 그 코드는 무슨 일이든 할 수 있습니다. GPU 래스터화는 훨씬 표준화되어 있습니다. 각 픽셀 좌표에서 삼각형을 어떻게 샘플링해야 하는지(애초에 삼각형을 샘플링한다는 사실까지) 표준이 정해 놓았죠.

    GPU 렌더링에서는 과정 전체가 표준화되어 있어서, 렌더링과 콘텐츠 제작 방식이 예전과 상당히 다릅니다. 물론 가능은 하지만, 요즘 ‘레트로’ 게임은 대개 GPU 가속 게임처럼 렌더링하면서 텍스처 필터링만 끄는데, 그래서는 그 룩이 제대로 나오지 않습니다.

    그는 Build 게임도 언급하는데, 이쪽은 엄밀히 말해 폴리곤 기반이 아닙니다.

    GPU에서도 범용 연산이 가능하고 표준 래스터화 파이프라인을 소프트웨어로 구현할 수도 있지만, 그래픽 카드는 사람들을 하나의 ‘룩앤필’로 밀어 넣었습니다. 표준화된 데이터 구조, 콘텐츠 제작 방식, 렌더링 방식에서 어느 정도 비롯된 룩이고, 그 때문에 게임들이 비슷비슷하게 느껴지게 되었습니다.

    반면 소프트웨어 렌더링에서는 본질적으로 특정 색으로 칠할 수 있는 픽셀 배열이 있을 뿐이고 나머지는 전부 개발자 몫이었습니다. 그래서 개발자들이 저마다 독특한 방식으로 렌더링하고 콘텐츠를 만들게 됐습니다.

    BSP 기반 도구로 가능해진 정교한 레벨 디자인은 소프트웨어에서는 사실상 ‘공짜’로 렌더링할 수 있었지만, GPU로 넘어오는 데는 엄청나게 고생했습니다. 픽셀을 그리면서 트리를 순회하는 방식에 의존하기 때문입니다. GPU에는 삼각형 목록을 제출해야 하니까요(GPGPU로 가지 않는다면요).

  28. kmeisthax HN

    현대 게임이 비슷비슷해 보이는 건 다들 같은 결과, 즉 포토리얼리즘을 원하고, UE5나 Unity 같은 기성 솔루션을 쓰는 데 까다롭게 굴지 않기 때문입니다. 렌더링 파이프라인에 대한 통제력은 완전히 미친 수준이고 거의 모든 게 프로그래밍 가능합니다. GPU는 병렬 프로그래밍이 끝없이 샘솟는 샘이죠. 하지만 그걸 활용하려면 실제로 엔진 코드를 고치거나 자체 렌더러를 짜야 하는데, 게임 쪽에서는 UE5 기본 룩이 “충분히 괜찮으니까” 신경 쓰지 않습니다.

    어느 정도 표준화는 있습니다. 지오메트리를 보내고 텍스처를 평범하게 샘플링하면 래스터화와 텍스처 샘플링을 반드시 하드웨어 유닛에 의존하게 되는데, 이 유닛들은 동작 방식에 대한 선택지가 비교적 제한적입니다. 하지만 그 선택지는 3D 엔진이 원할 만한 것을, 온갖 꼼수를 쓰던 초기 3D 엔진의 것까지 대부분, 아니 거의 전부 아우릅니다. GPU가 하드웨어로 실제 래스터화할 수 없는 것은 NURBS나 메타볼 같은 것인데, 업계가 아주 일찍부터 실시간 3D의 매체로 삼각형[0]을 택했기 때문에 문제가 되지 않습니다.

    옛 3D 게임 룩의 상당 부분은 편한 길로 갔기 때문인데, 자랑스럽게 말하건대 그 편한 길은 지금도 얼마든지 지원됩니다. 나중에 생긴 기능을 끄고 스스로 제약을 두기만 하면요. 저는 옛 DOS 게임을 잘 몰라서 PS1을 예로 들겠습니다. “PS1 룩”은 극도로 제한적인 래스터라이저의 결과물이고, 그 제약은 현대 하드웨어에서 간단히 흉내 낼 수 있습니다.

    1. 뎁스 버퍼를 할당하거나 바인딩하지 마세요. 매 프레임 지오메트리를 Z 정렬해야 합니다. “PS1 룩”의 일부는 일부 게임이 이걸 잘못 했다는 점이니, 제대로 하면 시각적 이득도 없이 성능만 태우게 됩니다. 보기에 재미있게 깨지는 방식을 고르세요. 마찬가지로 PS1도 안티앨리어싱을 하지 않았으니 멀티샘플링은 절대 할당하지 마세요.

    2. 버텍스 셰이더에서 정점 좌표를 반올림해 정밀도를 제한하세요. PS1은 정점 연산을 극도로 낮은 정밀도로 했고, 그 때문에 정점이 흔들립니다.

    3. 프래그먼트 셰이더에서 UV 정밀도를 제한하세요. PS1의 UV는 0~255였으니 1/256 정밀도를 원하는 겁니다.

    4. 뷰포트 가장자리에 걸치는 지오메트리는 과감히 버리세요. PS1에는 제대로 된 폴리곤 클리핑이 없었습니다. 뷰포트 끝을 넘어 렌더링할 수 없었기 때문에, 게임들은 물체가 사라지지 않도록 지오메트리를 버리고 폴리곤을 쪼개야 했습니다. 이것도 “잘하면 아무도 눈치채지 못하는” 부류입니다.

    5. 원근 보정 속성 보간을 끄세요. 그러면 UV가 특정 각도에서 휘고 일그러집니다. GLSL에서는 noperspective 속성으로 할 수 있습니다.

    6. 텍스처를 샘플링할 때 바이리니어 필터링을 끄세요.

    7. 저정밀도 컬러 버퍼를 할당해 거기에 렌더링하세요. 많은 게임이 쓸 수 있는 VRAM에 맞춰 색 깊이를 바꿨습니다. VRAM 사용량을 직접 세면서 현실적인 값을 지키지 않는다면 이 부분이 아마 흉내 내기 가장 어려울 겁니다.

    8. 텍스처를 업로드하기 전에 비트크러시하세요. 이유는 같습니다. 밉맵도 업로드하지 마세요.

    9. 조명은 CPU에서 계산한 버텍스 컬러로 처리했습니다. CPU 조명에 얽매일 필요는 없지만, 조명이 버텍스 셰이더에서 처리되도록은 해야 합니다.

    10. 선택 사항: 프래그먼트 셰이더에 초기 모델 PS1의 텍스처 밴딩 버그를 구현하세요.

    이렇게 하면 비트 단위로 정확한 PS1 에뮬레이션만 빼고 거의 모든 용도에 쓸 만한 렌더링 파이프라인이 됩니다. 물론 비트 단위로 정확한 에뮬레이션을 한다면, 화면을 가득 채우는 쿼드를 그리고 GPU에서 소프트웨어 래스터화를 하는 셰이더를 짜는 걸 막을 사람은 없습니다. 아니면 프레임버퍼에 임의로 접근하는 컴퓨트 셰이더를 실행할 수도 있습니다. 다만 워프와 워크그룹 사이의 메모리 일관성은 신경 써야 합니다. 텍스처 샘플링도 원한다면 “소프트웨어로” 할 수 있습니다. 텍스처에서 필터링 없는 읽기(사실상 배열 조회)를 한 뒤 원하는 대로 필터링하면 됩니다.

    [0] 한때 쿼드가 이길 수도 있었던 짧은 시기가 있었습니다. 아티스트는 쿼드로 작업하기를 좋아하고 엔비디아도 쿼드 하드웨어를 내놓고 있었으니까요. 하지만 그 이점은 금방 사라졌고, 쿼드는 삼각형으로 쉽게 바꿀 수 있지만 그 반대는 그렇지 않습니다.

  29. torginus HN

    설명하려는 의도는 좋았겠지만, 제 요점을 놓치셨습니다.

    옛날 룩이라는 게 하나로 정해져 있지 않습니다. 소프트웨어 렌더러를 짜던 사람들에게 공인된 방식이 없었고, 상당수는 삼각형조차 쓰지 않거나 여러 기법을 섞어 썼습니다.

    지금 PS1 게임처럼 보이게 만드는 법을 설명하셨는데, PS1에는 다소 결함은 있어도 독특한 GPU가 있었고 그 GPU가 여기 적으신 제약을 사실상 강제해서 렌더링 방식이 하나로 정해졌습니다.

    Quake나 Duke3D, 혹은 PC 전반에서는 그렇지 않았습니다. 어떤 기법을 고르든 그냥 화면에 그리면 됐고, 그래서 게임마다 고유한 룩이 나왔습니다.

  30. fmbb HN

    저자는 30년 전 실시간 3D 게임에서 CPU 성능의 한계가 렌더링 모델에 강요한 절충의 룩을 좋아하는 거예요.

    네, 설정을 손보거나 직접 게임을 만들어서 그 룩을 흉내 낼 수는 있죠.

    글을 읽어 보면 텍스처 샘플링만이 아니라 색 깊이 얘기도 있다는 걸 아실 거예요.

    OP는 제목을 잘 골랐어요. 아무도 신경 쓰지 않는 의미론으로 싸우면서 더 복잡한 제유(synecdoche)를 고르고 계시네요.

Hacker News에서 보기 ↗