Wenn du für jede unterstützte Grafikschnittstelle von vornherein alle möglichen Besonderheiten und Möglichkeiten unterstützen willst und diese evtl. sogar iim Programmcode der Applikation selbst "switchen" willst, kann das auch ziemlich in der Sackgasse enden, da man die Übersicht verliert und der eigentliche Programmcode von Branches zugemüllt wird.
Ich würde das wohl eher in zwei Schritten machen:
1) Erst einmal den "kleinsten gemeinsamen Teiler" aller Grafikschnittstellen ermitteln, für alle Primitive und "Objekte", die du unterstützen willst und setzt diese auf die am effizientesten mögliche Art und Weise in separaten Klassen um, die nach aussen jedoch eine konsistente
API an die Wrapper-Klasse / deine Engine liefern, z.B. eben Routinen wie DrawPolygon DrawRect oder Subklassen wie new FragmentShader etc.
2) Hast du diese implementiert und bekommst schon mal auf allen Schnittstellen ein ähnliches Bild, dann kannst du die entsprechenden Draw-Routinen für
jede Schnittstelle individuell auch noch mit Schnittellen-spezifischen Befehlen aufhübschen oder erweitern.
Anders machen es die "großen" Engines auch nicht, es wird für jede Schnittstelle einfach das "Ziel" individuell in den Low-Level-Routinen / Klassen umgesetzt, der eigentliche Spiele- / Applikationscode nutzt jedoch einen Layer darüber, der über Interfaces arbeitet, die sicherstellen, dass nach aussen die
API für alle unterstützten Schnittstellen exakt gleich ist.
Das hat auch den Vorteil, für die Zukunft auch zusätzliche Grafikschnittstellen einbinden zu können, ohne dich beim Programmcode mit der eigentlichen, darunterliegenenden Schnitstelle befassen zu müssen oder Code am Programm selbst ändern zu müssen.
Willst du z.B. so etwas wie Mantle später unterstützen, baust du dir dafür "einfach" (haha!) eine Klassenstruktur, die deine Engine-
API abbildet und du musst nichts am Programm selbst ändern.
Die Grafikschnittellen-spezifischen Optionen würde ich eher nutzen, um entweder performance-technisch die ein oder andere Schnittstelle zu optimieren oder um einige Sachen einfach "schöner" als in der anderen Schnittstelle erscheinen zu lassen.
Z.B. sagt dein Programm nur "new Snow()" o.Ä. Wie der Schnee dann aber aussieht und welche Schnittstellen-spezifischen Effekte er benutzt, kannst du in der eigentlichen Klasse schreiben, die direkt auf die Grafikschnitstelle zugreift.