How much data is passed from the CPU to the GPU per frame depends on the engine you are using and its needs. Usually, you want to keep it minimal to avoid that the GPU needs to wait for data because the CPU can't deliver it fast enough. I can not guarantee you, that the following is always the same for all engines and graphic APIs since I have only written OpenGL code myself so far, but to my knowledge, it is quite similar.
What you usually do is that you send all data that doesn't change to the GPU at the beginning. Objects (meshes) and textures are typical candidates for this. Animated meshes like characters can also be loaded in advance by providing a default position.
The object data consists of vertices. They are control points that store the position of each point and an arbitrary set of additional information. A typical additional piece of information might be a color, surface normal, or texture coordinate (this does not specify which texture). Note, that there is no information at this point, how those vertices are connected. This is why I put "meshes" into parenthesis in the previous section.
Now you have a bunch of vertex groups and textures and it is time to render our scene. We assume, that all work that is not related to rendering is done by the CPU. Most of it can also be done by the GPU, but lets keep it simple.
The CPU calculates where every object is positioned in the current frame, including the camera. It also calculates the skeletal state of our animated characters. Now, to render a single object, we need to provide the following information to the GPU:
- the object data we want to use
- the textures we want to use
- connectivity of the vertices
- the shader program we want to use
- the data the shader program requires to do its job (usually some transformation matrices and vectors)
The first two are just IDs we got from our API/engine when we uploaded the data. You should know for all your objects, which mesh and texture IDs they use. The third point, connectivity, actually tells the GPU how the single vertices are connected to each other. In OpenGL, there are several ways to specify this. A lot of them assume a specific order in the data, for example that 3 vertices in a row represent a single triangle. But there are also explicit ways by providing an index list to the GPU (This is usually also done at startup).
Now that the GPU knows which data to use and how it is connected, it needs to know what to do with it. This is done by selecting a shader program.
Shaders are small programs that run on the GPU. They have several stages that turn your mesh into actual pixels on your screen (see OpenGL rendering pipeline). You usually compile several of them at the beginning when uploading all the other data. Those programs generally also need some additional information to run and you need to pass that to the GPU. What that data is, strongly depends on the effects you want to use, but something you need very often are the transformations needed to place a vertex in relation to the camera and the projection you want to use (perspective, parallel). Now the shader program can calculate each vertex position on the screen, create triangles from it and color each of its pixel as desired (using the textures you specified). You do this for every object in your scene (that is not culled).
To sum it up:
For a single object in a basic OpenGL engine, you pass the IDs of its vertex data, textures and shader program to the GPU, which are just numbers. Then you pass all data needed by the shader program. This is mostly a bunch of floats for the transformations. Finally, you also tell the GPU how the data is connected for your current "draw call". There is also not much data transfer involved here, since you either have your data ordered in a specific way and need to tell the GPU just which method to use or you have uploaded a connectivity list upfront and tell the GPU the ID of the index list it should use.
Please note, that the described procedure is based on the approach that is described in most tutorials. Modern engines use several optimization techniques to balance CPU and GPU processing power so that they are always busy. This might require more involved data transfer. Also note, that the data transfer itself has bandwidth limitations that need to be considered.