O nível mais baixo que faz sentido do meu ponto de vista é algo que fala sobre os recursos envolvidos na renderização - vb / ib, superfícies de renderização, texturas, shaders, blocos de estado etc.
O problema aqui é que alguns deles precisam estar em formatos diferentes, dependendo da API - é aí que fica um pouco complicado. A maneira mais fácil de contornar isso é pré-processar os recursos estáticos para a respectiva API. Para os dinâmicos, use apenas shaders para gerá-los - o que torna bastante simples permanecer em formatos nativos.
Tudo o que você faz no nível superior é configurar pipelines com recursos anexados e entregá-los à GPU. Você descobrirá que nem tudo pode ser abstraído dessa maneira, especialmente se você aproveitar os truques específicos do hardware. Mas é um bom começo.
(Nota: se você tratar os truques específicos da plataforma como um tipo especial de recurso, poderá levar esse conceito muito longe).
Então, de certa forma, você criará duas coisas: um gerenciador de recursos de hardware, além de um kit de ferramentas para configurar um DAG desses recursos.