- paraLLEl-GS จำลอง GS (Graphics Synthesizer) ของ PS2 ด้วย Vulkan compute เพื่อจัดการข้อจำกัดด้านความถูกต้องและการอัปสเกลที่ GSdx ซึ่งเป็นมาตรฐานโดยพฤตินัยมาเกือบ 20 ปีทิ้งไว้
- GS ทำงานบน VRAM 4MiB และ fill rate สูง แต่ด้วยคุณสมบัติของ pixel pipeline เช่น destination alpha test, conditional blending และค่า alpha/color ที่เกิน 1.0 จึงทำให้ยากที่จะทำให้ตรงด้วย graphics API ทั่วไป
- การใช้งานติดตาม VRAM เป็นหน่วย page และ block ขนาด 256 ไบต์ และผสาน CLUT snapshot, texture unswizzle และการจัดชุด render pass เพื่อจัดการ framebuffer/texture feedback
- มีการเปรียบเทียบปัญหาด้านการอัปสเกล, UI, high-precision blending และ texture feedback ในกรณีอย่าง Tales of the Abyss, Final Fantasy X, MGS2, Valkyrie Profile 2 และ Shadow of the Colossus รวมถึงฉาก 8x/16x SSAA
- ปัจจุบันการตรวจสอบอาศัยการเล่นซ้ำ GS dump เป็นหลัก แม้จะมี patch แบบแฮ็กสำหรับ PCSX2 และการทดสอบเรียลไทม์ด้วย mkfifo แต่หากต้องการให้ถึงมือผู้ใช้จริง ยังจำเป็นต้องผสานเข้ากับ emulator
เป้าหมายและจุดเริ่มต้นของ paraLLEl-GS
- paraLLEl-GS เป็นโปรเจกต์จำลอง GS (Graphics Synthesizer) ของ PlayStation 2 ด้วย Vulkan compute
- งานปี 2020 ของผู้เขียนคนเดียวกันอย่าง paraLLEl-RDP ได้นำ N64 RDP มาใช้งานด้วย Vulkan compute โดยตั้งเป้าให้ได้ผลลัพธ์ใกล้เคียง bit-exact เมื่อเทียบกับ Angrylion และรองรับการอัปสเกล
- สำหรับ PS2 นั้น GSdx ยังคงเป็น implementation ระดับแนวหน้าตามพฤตินัยมาเกือบ 20 ปี
- ราวปี 2014 เคยมีความพยายามทำ PS2 GS compute implementation บน OpenCL แต่ไม่เสร็จสมบูรณ์ และปัจจุบันก็หาได้ยากใน upstream repository
- เหตุผลที่ต้องใช้ compute shader raster บน PS2 นั้นอ่อนกว่า N64
- PCSX2 มี software renderer ที่ปรับแต่งมาดี และ renderer แบบใช้กราฟิกที่ค่อนข้างแข็งแรง
- software renderer ไม่รองรับการอัปสเกล
- renderer แบบใช้กราฟิกมีบั๊กและ glitch หลายอย่าง โดยเฉพาะเมื่ออัปสเกล
- paraLLEl-GS เน้นหลีกเลี่ยงปัญหาความถูกต้องที่เห็นได้ชัด มากกว่าการทำให้ bit-exact กับฮาร์ดแวร์
- software renderer ของ GSdx เองก็ดูไม่ใช่ implementation ที่ bit-exact กับฮาร์ดแวร์ ทำให้การทดสอบแบบเทียบกันโดยตรงชนขีดจำกัดอย่างรวดเร็ว
ทำไม PS2 GS จึงรับมือยาก
- ในปี 2000 GS เป็นอุปกรณ์ที่ตามทฤษฎีมี fill rate และ bandwidth ที่ประมวลผลได้มากกว่า 1 พันล้านพิกเซลต่อวินาที
- VRAM มีขนาดเล็กเพียง 4MiB แต่ถูกออกแบบให้สตรีมข้อมูลอย่างต่อเนื่องผ่าน DMA engine หลายตัว
- pixel pipeline เองมีบางด้านที่เรียบง่ายกว่า N64 RDP
- texture เดียว
- single-cycle combiner
- anti-aliasing แบบพื้นฐานมาก
- มีฟีเจอร์จำนวนมากที่ทำได้ยากด้วย graphics API ทั่วไป
- blending ที่เกิน 1.0: เป็นพฤติกรรมที่สืบต่อมาจาก PS1 โดย 0x80 ถูกปฏิบัติเสมือน 1.0 และสามารถแสดงค่าได้ถึง 0xff
- destination alpha test: สามารถใช้ destination alpha คล้าย stencil ได้
- conditional blending: สามารถปิด blending แบบมีเงื่อนไขตาม alpha ได้
- alpha correction: ก่อนบันทึก alpha สามารถ OR ค่า MSB เพื่อบังคับให้เข้าใกล้ 1 ได้
- การ discard บางส่วนของ alpha test: ทำพฤติกรรมแบบทิ้งเฉพาะสี แต่ยังคงบันทึก depth ได้
- AA1: เป็นแบบ coverage-to-alpha และเกี่ยวพันกับการควบคุมการบันทึก depth รายพิกเซล
- Z แบบ fixed-point 32 บิต: แม้ D32_UINT จะรองรับในทางเทคนิค แต่ยังไม่พบกรณีใช้งานจริง
- หากไม่มี programmable blending บน immediate-mode desktop GPU จะต้องใช้ ROV หรือ barrier รายพิกเซล ทำให้ประสิทธิภาพแย่ลงมาก
- implementation แบบ compute สร้าง tile-based deferred renderer (TBDR) ของตัวเองเพื่อเลี่ยงข้อจำกัดเหล่านี้
กฎ raster, vertex queue และการจัดวางหน่วยความจำ
- primitive ของ GS ถูกส่งมาใน clip space ค่อนข้างธรรมดา
- VU1 ทำการ transform และ clipping แล้วส่งออก vertex attribute หลายอย่างไปยัง GS
- พิกัดและ attribute มีรูปแบบเฉพาะของ GS
- X/Y: unsigned fixed-point 12.4
- Z: uint 24 บิตหรือ 32 บิต
- FOG: uint 8 บิต
- RGBA: ค่า 8 บิตสำหรับแสงต่อ vertex
- STQ: พิกัด texture แบบ perspective-correct
- UV: พิกัดที่ไม่ normalize แบบ fixed-point 12.4 โดยไม่มี perspective correction
- กฎ raster ใกล้เคียงสไตล์ D3D9
- triangle ใช้กฎ top-left raster เหมือน GPU สมัยใหม่
- จุดกึ่งกลางพิกเซลอยู่ที่พิกัดจำนวนเต็มเหมือน D3D9
- line ใช้อัลกอริทึม Bresenham จึงอัปสเกลได้ยาก และต้องประมาณด้วย rect หรือ parallelogram
- point จะ snap ไปยังพิกเซลที่ใกล้ที่สุด
- sprite เป็น quad แบบง่ายที่มีสองพิกัด
- vertex queue ของ GS คล้าย immediate mode ของ OpenGL 1.0
- ตั้งค่า RGBA, STQ และ register หลายตัว แล้วการเขียน XYZ register จะกลายเป็น vertex “kick”
- รองรับ TRIANGLE_FAN ด้วย
- พิกัดพิกเซลของ PS2 ถูกจัดวางเป็นหน่วย page
- หนึ่ง page มีขนาด 8KiB
- page ถูกแบ่งเป็น 32 block
- สำหรับ RGBA 32 บิต หนึ่ง page คือ 64×32 พิกเซล และ block ขนาด 8×8 จำนวน 32 block จะถูก swizzle แบบ Z-order
- เมื่อ render เป็นสี 24 บิตหรือ depth 24 บิต สามารถวาง texture ไว้ใน 8 บิตบนสุดที่เหลือได้
- format 8H, 4HL, 4HH มีประโยชน์สำหรับ palette แบบ 8 บิตและ 4 บิต
Texture, CLUT และ TEXFLUSH
- texture ของ GS ผสมทั้งส่วนที่คล้าย API สมัยใหม่และส่วนที่แปลกเฉพาะตัว
- จุดกึ่งกลาง texel อยู่ที่ half-pixel เหมือน API สมัยใหม่
- ความละเอียดระดับ sub-texel ดูเหมือนเป็น 4 บิต ไม่ใช่ 8 บิต
- bilinear filter เป็น bilinear ทั่วไป ไม่ใช่โครงสร้างพิเศษอย่าง 3-point filter ของ N64
- addressing mode พิเศษทำให้ความยากในการ implementation สูงขึ้น
- REGION_CLAMP สามารถใช้ clamp กับพื้นที่任意ภายใน texture atlas ได้
- REGION_REPEAT ทำ bit operation เช่น
(u & MASK) | FIXกับแต่ละพิกัดได้ จึง implement ได้ยากกว่า
- mipmapping คำนวณ LOD จาก log2 ของ Q factor ที่ interpolate แล้วและ scale coefficient แทนการใช้ derivative
- ใน compute implementation การไม่พึ่ง derivative ถือเป็นข้อได้เปรียบใหญ่
- วิธีนี้ไม่สามารถรองรับฟีเจอร์อย่าง anisotropic filtering ได้
- CLUT คือ cache ขนาด 1KiB ที่เก็บ palette ปัจจุบัน
- ต้อง copy จาก VRAM ไปยัง CLUT cache อย่างชัดเจนจึงจะใช้งานได้
- สำหรับสี 32 บิต สามารถเก็บ palette 256 สีได้หนึ่งชุด
- สำหรับ 16bpp สามารถเก็บ palette 16 สีได้ 32 ชุด
- TEXFLUSH เป็นคำสั่ง explicit ที่ใกล้เคียงกับการซิงก์และ invalidate texture cache
- ช่วงแรกของ implementation ตั้งใจจะใช้ TEXFLUSH เป็นเกณฑ์สำหรับ hazard tracking แต่สุดท้ายต้องเพิกเฉย
- มีปัญหาที่เกมลืมเรียก TEXFLUSH หรือเรียกบ่อยเกินไป
- implementation สุดท้ายเลือกแนวทาง minimal caching
- ถือว่าไม่มี cache แล้วติดตาม hazard โดยตรง
- พิจารณาการจัดการข้อยกเว้นแยกต่างหากสำหรับ feedback loop
- GSdx ก็ดูเหมือนไปในทิศทางเดียวกัน
Rendering pipeline แบบ Vulkan compute
- pipeline ของ implementation ถูกออกแบบโดยมีการซิงก์ระหว่างแต่ละขั้นเป็นพื้นฐาน
- ซิงก์สำเนา VRAM ฝั่ง CPU กับ GPU
- อัปโหลด VRAM หรือทำ local-to-local copy
- อัปเดต CLUT cache จาก VRAM
- unswizzle VRAM เป็น VkImage เพื่อให้ sample ได้โดยตรง
- ทำการ rendering
- ซิงก์สำเนา GPU VRAM กลับไปยัง CPU
- พฤติกรรมทั่วไปของเกมเข้ากับ pipeline นี้ได้ดี
- อัปโหลด texture ไปยัง VRAM
- อัปโหลด palette ไปยัง VRAM
- อัปเดต CLUT cache
- draw ด้วย texture
- หากจำเป็น ให้ unswizzle จาก VRAM เป็น VkImage
- จัดชุด primitive เป็น render pass
- หากไม่มี hazard ที่ย้อนกลับ สามารถหน่วงการจัด batch และการซิงก์ได้
- สำหรับ renderer แนวนี้ การ รักษา batch ไว้เป็นสิ่งสำคัญต่อประสิทธิภาพ
- กรณี hazard หลักถูกจัดการแยกต่างหาก
- copy ซ้ำไปยัง VRAM ที่ถูกเขียนด้วย copy ไปแล้ว
- copy ไปยัง VRAM ที่ถูกอ่านโดย texture หรือ CLUT ที่ถูก sample
- sample พื้นที่ที่ render แล้วเป็น texture
- copy ไปยัง VRAM ที่ render แล้ว
การติดตาม page และ texture cache
- ส่วนที่ยากที่สุดของการจำลอง GS คือการจัดการ hazard แบบ read-after-write และ write-after-write ใน VRAM
- VRAM 4MiB ถูกแบ่งเป็นหน่วย page ก่อน
- page เป็นหน่วยของ framebuffer และ depth buffer จึงเป็นเกณฑ์ติดตามที่มีความหมายที่สุด
- สถานะที่ติดตามในระดับ page มีดังนี้
- pending frame buffer write
- pending frame buffer read
- texture และ VRAM copy มี alignment 256 ไบต์ จึงใช้ bitmask แบบ u32 สำหรับ block 32 ก้อน
- VRAM copy write
- VRAM copy read
- pending read ไปยัง CLUT cache หรือ VkImage
- block ที่ถูกเขียนทับด้วย write ใด ๆ
- ในกรณีที่ render เป็นสี 24 บิตแล้ว sample 8 บิตบนสุดเป็น texture อาจไม่มี hazard
- เพื่อสิ่งนี้ จึงติดตาม framebuffer write mask และ texture read mask แยกกัน
- แต่ละ page มีรายการ VkImage ที่เชื่อมโยงอยู่
- เมื่อ texture ของ page ถูก invalidate ภาพจะถูกทำลายและต้อง unswizzle ใหม่จาก VRAM
- texture หนึ่งตัวอาจครอบคลุมหลาย page และหาก page ใด page หนึ่งถูกเขียนทับ texture ก็จะถูก invalidate
- การติดตามแบบเรียบง่ายและ conservative อย่างเดียวไม่ทำงานกับเกม PS2
- การติดตามระดับ block 256 ไบต์ และการพิจารณา write/read mask เป็นสิ่งสำคัญ
- อาจเกิด false positive เนื่องจาก POT texture และการไม่ใช้ REGION_CLAMP
- ตัวอย่างเช่น หากตั้ง render target 512×448 เป็น texture 512×512 พื้นที่ที่ไม่ได้ใช้อาจดูเหมือนเป็น hazard
- implementation ใช้วิธี workaround ที่เพิกเฉยต่อ hazard ที่อาจเกิดขึ้นใน “red zone” นั้น
การจัด batch ของ CLUT และ texture unswizzle
- หากต้องการ batch การอัปโหลด texture ก็ต้อง batch การอัปโหลด CLUT ไปพร้อมกันด้วย
- implementation มีสำเนา CLUT 1024 ชุดเป็น snapshot ring buffer
- workgroup หนึ่งวนอัปเดตและเขียนลง SSBO
- คล้ายกับการอัปเดต TMEM ของ N64 RDP แต่การอัปเดต CLUT เรียบง่ายกว่ามาก
- ใน Vulkan จะ allocate VkImage ใหม่ sub-allocate จาก VkDeviceMemory แล้วใช้ compute shader ทำ unswizzle
- ใช้ Vulkan specialization constants เพื่อ specialize format ของ texture และ logic การ swizzle
- พฤติกรรมพิเศษของ REGION_REPEAT ก็ถูกจัดการในขั้นตอน unswizzle ด้วย
- หลังจากนั้น ubershader จึงลดความจำเป็นในการคำนึงถึงกรณีนี้เพื่อทำ manual bilinear filtering
- render target ก็เดินทางไปกลับผ่าน VRAM SSBO เป็น texture
- ความพยายามที่จะ forward render target เป็น texture โดยตรงถูกตัดสินว่ามีบั๊กและข้อยกเว้นมากเกินไป
Triangle setup, binning และ ubershader
- paraLLEl-GS เป็น tile-based renderer เช่นเดียวกับ paraLLEl-RDP
- ทำ triangle setup ก่อน binning โดย input แบ่งเป็นสาม array
- ตำแหน่ง
- attribute ต่อ vertex
- attribute ต่อ primitive
- rasterizer อิง barycentric และได้รับอิทธิพลอย่างมากจากวิธี parallel rasterization ที่อธิบายในบทความ graphics pipeline ของ Fabian Giesen และงานวิจัย Pineda 1988
- PS2 GS จริงเป็น DDA หรือ scanline rasterizer แต่เนื่องจากไม่ทราบคำอธิบายแบบ bit-exact ของ GS DDA จึงใช้วิธี barycentric
- รองรับ parallelogram ด้วยเพื่อทำ wide line และ sprite
- inv_area คำนวณด้วย RCP แบบ fixed-point ที่กำหนดเอง
- เลี่ยง RCP มาตรฐานของ GPU เพราะความสม่ำเสมอขึ้นกับ implementation และมีความแม่นยำประมาณ 22.5 บิต
- RCP ที่กำหนดเองตั้งเป้าความแม่นยำประมาณ 24.0 บิต
- binning ปกติใช้ block ขนาด 32×32 พิกเซล
- จำนวน primitive สูงสุดต่อ render pass คือ 64k เนื่องจากดัชนี u16
- render pass หลักที่พบโดยทั่วไปอยู่ในช่วง 10k~30k primitive
- PS2 GS มี fill rate สูงและความซับซ้อนต่อพิกเซลต่ำ จึงสามารถใช้ ubershader ล้วน ได้
- ต่างจาก N64 ตรงที่สามารถใช้ bindless ได้ ทำให้ความซับซ้อนของ texturing ลดลงด้วย
- ubershader ใช้ early Z, deferred on-tile shading และ lazy pixel shading
- จะทำ shading จริงเฉพาะเมื่อพิกเซลขึ้นกับผลลัพธ์ก่อนหน้าเท่านั้น
- alpha test, color write mask, alpha blending เป็นต้น สร้าง dependency แบบนี้
- สี framebuffer สุดท้ายและ depth ถูกบันทึกลง SSBO เพื่อลดการใช้ GPU bandwidth
Supersampling และการลด artifact จากการอัปสเกล
- หาก render แบบ sample เดียวอย่างเดียว renderer นี้ยังมีประโยชน์ไม่เพียงพอ
- ตัวอย่างเช่น ใน 8x SSAA จะเก็บ VRAM 10 เวอร์ชันไว้บน GPU
- VRAM sample เดียว 1 ชุด
- ค่า reference ของ VRAM sample เดียว 1 ชุด
- supersample 8 ชุด
- เมื่อ render หาก VRAM sample เดียวตรงกับ reference จะโหลดเวอร์ชัน supersample
- สำคัญต่อ incremental rendering
- เมื่อ tile เสร็จ จะทำ multisample resolve ด้วย clustered subgroup operation แล้วบันทึก supersample และสำเนา sample เดียว
- supersampling มีรอยหยักน้อยกว่าการอัปสเกลแบบง่าย และทำให้ความรู้สึกด้านความละเอียดขององค์ประกอบ 3D กับ UI สอดคล้องกันมากขึ้น
- sprite primitive ต้อง render แบบ single-rate เสมอ
- ส่วนใหญ่เป็น UI หรือองค์ประกอบคล้ายกัน และหากอัปสเกล อาจ sample นอก rect ที่ตั้งใจไว้หรือเบลอเกินไปจาก bilinear filtering
- UI จำนวนมากถูกวาดด้วย triangle ปกติ ดังนั้น primitive แบบ flat บางส่วนจึงลดการ interpolate attribute ลงสู่พิกัด single-pixel
- แม้ใช้ perspective แต่หาก Q ของทุก vertex เท่ากันและ Z ก็เท่ากัน จะสันนิษฐานว่าเป็น flat UI primitive
- อาจมี false positive ได้ แต่ในเกมที่ทดสอบ วิธีนี้ทำงานได้ดีพอ
ผลลัพธ์ตามเกมและกรณียาก
- ใน Tales of the Abyss พบการจัดแนว bloom ของ Yulia ที่ไม่ตรงและลวดลายสี่เหลี่ยมในอัปสเกลของ PCSX2 Vulkan backend
- ใน 8x SSAA ของ paraLLEl-GS ปัญหาทั่วไปของการอัปสเกลที่ไม่ดีไม่ค่อยปรากฏ
- screenshot ดังกล่าวยังใช้ post-process upscale ของ FSR1 ด้วย
- UI ของ Final Fantasy X แสดงปัญหาการอัปสเกลเมื่อเทียบ native resolution กับ 4x upscale
- MSAA snap trick มีประสิทธิภาพในการหลีกเลี่ยง artifact
- หลักสำคัญคืออย่าอัปสเกล UI เกิน nearest neighbor integer scale
- MGS2 มีกรณีที่ต้องการ blending accuracy สูงใน PCSX2
- PCSX2 ใส่ barrier ต่อ primitive ใน programmable blending path ทำให้ประสิทธิภาพตกลงมาก
- paraLLEl-GS มีโครงสร้างที่ทำงานด้วย blend accuracy 100% เสมอ และมีการยกตัวอย่างฉาก 16x SSAA บน RX 7600 ที่ใช้ 25W และ GPU utilization 17%
- Valkyrie Profile 2 มีกรณี sample alpha ของพิกเซลตัวเองเป็น palette index
- paraLLEl-GS ตรวจจับสิ่งนี้ ทำให้ texture index เป็นค่าพิเศษ และอ้างอิง in-register framebuffer color
- การปรับแต่งนี้ลด render pass barrier จากมากกว่า 500 เหลือ 18
- เอฟเฟกต์ camo ใน intro ของ MGS2 sample framebuffer เป็น texture แต่ใช้พิกัดซ้อนทับที่การจัดแนวพิกเซลไม่ตรงกัน
- PCSX2 ก็ดูเหมือนไม่เพิ่ม barrier ที่นี่ และ paraLLEl-GS ก็จัดการแบบเดียวกัน
- Shadow of the Colossus ทำหน้าที่เป็น stress test ที่หนัก
- ใน PCSX2 ที่ maximum blend accuracy แค่ 2x upscale ใน intro ก็ทำให้ GPU ลดลงเหลือ 24 FPS
- paraLLEl-GS ยังมีประสิทธิภาพดีแม้ที่ 8x SSAA แต่ในฉากดังกล่าวภาระจะหนักขึ้น
- กรณีนี้คอขวดอยู่ที่ geometry processing ของ CPU มากกว่า GPU
สถานะปัจจุบันและขั้นตอนถัดไป
- วิธีทดสอบที่ใช้งานได้จริงในปัจจุบันคือการใช้ GS dump
- มี hack-patch ที่ทำให้ PCSX2 dump raw GS trace ได้
- สามารถทดสอบเรียลไทม์แบบหยาบ ๆ ผ่าน mkfifo ได้เช่นกัน
- หากต้องการให้เป็นประโยชน์กับผู้ใช้ปลายทาง จำเป็นต้องมีการผสานเข้ากับ emulator ในรูปแบบใดรูปแบบหนึ่ง
- เนื่องจากไลบรารีของ PS2 มีขนาดใหญ่มาก จึงมีความเป็นไปได้สูงว่ายังมีบั๊กจำนวนมากซ่อนอยู่
- ด้วยลักษณะเป็น standalone library จึงยังมี use case ที่เป็นไปได้ในการใช้งานเหมือน rendering API แบบเก่า
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
รู้สึกว่าต้องหาอยู่นานว่าในบทความนี้ตัวย่อ GS ถูกขยายความไว้ตรงไหน เนื้อหาน่าสนใจ แต่ตรงนี้ทำให้รู้สึกตามไม่ค่อยทัน
ประโยค “จงภาวนาให้มี programmable blending” นี่ หลังจากได้เรียน pixel shader ครั้งแรกช่วงต้นยุค 2000 ก็เฝ้าหวัง programmable blending ผ่าน “blending shader” มาตลอด
อีกอย่าง ยังอยากได้ programmable texture decoding ผ่าน “texture shader” ด้วย ซึ่งน่าจะมีประโยชน์กับรูปแบบ/การบีบอัด texture แบบกำหนดเอง การผสม texture ฯลฯ
กลายเป็นว่า GPU ได้ ray tracing มาก่อน programmable blending เสียอีก ทั้งที่อย่างแรกเหมือนความฝันคืนฤดูร้อน ส่วนอย่างหลังรู้สึกเหมือนแค่เปลี่ยนบล็อกฟังก์ชันตายตัวอีกตัวให้เป็นบล็อกที่โปรแกรมได้เท่านั้น ส่วน texture shader ก็ยังคงรอต่อไป
https://medium.com/pocket-gems/programmable-blending-on-ios-...
https://developer.apple.com/videos/play/tech-talks/605
มีตัวอย่างดี ๆ ที่ใช้สิ่งนี้: https://vulkan.org/user/pages/09.events/vulkanised-2024/vulk...
มองว่าพอจะ implement ได้ในระดับหนึ่ง แต่ถ้าไม่มี use case ที่น่าเชื่อถือ ก็คงยากที่จะให้เหตุผลรองรับงาน implement นี้
ส่วนที่ชอบที่สุดของ GS คือขนาดที่บ้าบอของ โครงสร้างบัส กว้างรวม 2560 บิต และการแบ่งแคชก็ฉลาดด้วย
PS3 ในบางแง่รู้สึกเหมือนถอยหลัง โดยเฉพาะเรื่อง blending
อยากรู้ว่าแนวทางนี้เทียบกับ ubershader ของ Dolphin ได้อย่างไร
จริง ๆ ตอนที่ Dolphin นำมาใช้ เทคนิคนี้ก็เก่าอยู่แล้ว โปรเจกต์นี้เป็นเรนเดอเรอร์เต็มรูปแบบที่รวมถึง rasterizer และอย่างที่เห็นในบทความก็มี ubershader สำหรับ blending ด้วย
shader ไม่ได้วาดสามเหลี่ยม แต่ถูกเรียกในแต่ละจุดภายในสามเหลี่ยม รับอินพุตบางอย่าง แล้วตัดสินสีของจุดนั้น คล้าย ๆ แบบเลือน ๆ กับการเทียบ emulator CPU ทั้งตัวกับสิ่งที่ implement แค่คำสั่ง ADD/MUL
สงสัยว่า top-left raster หมายถึงอะไร