You could use a rendering pass that writes “triangle IDs” to an offscreen framebuffer instead of rendering colors. Then you can read the pixels from the offscreen framebuffer and get the IDs of all visible triangles. That means you need to extend your vertex data to contain such triangle IDs, create a 32-bit-per-pixel framebuffer, and write a simple fragment shader that only writes the triangle IDs into the framebuffer (and discards color information etc). Depth-testing must of course be enabled.
Note that this solution might miss very small triangles that don’t even occupy a single pixel. It also won’t work properly with triangle strips (you’ll need “3-vertices-per-triangle” vertex buffers instead).