PIC pins are 'multi-use' and may be (default) assigned to USART OSC I2C etc etc ect ... if it's not that, then often the reason why i/o port bits get 'stuck' is down to the loading on the pin ...
However the trick is to recognise that you don't have time during the VGA 640 pixel visible line to 'count' or 'loop' at all .. and for sure not enough time to 'bit bang' an i/o pin !!!
However add an external shift register (74HC166) and set that up to 'load' 1 byte at a time from 8 PIC i/o pins and it becomes possible to show a 80 FONT mapped (8x8) ascii characters in each of 48 lines.
You clock the shift reg at 24MHz (which is within most VGA display limits) and run the PIC with a CPU clk (OSC/2) of 15 MHz (so OSC is 30MHz) and that gets you 'exactly' 5 CPU cycles to update the byte i/o pins.
5 CPU cycles means no counting, calling, returning or any other useless time wasting. Instead we 'build' the 8x8 character font table out of 'sets' of 5 instructions that output a byte to the i/o pins and then 'jump' to the next font location that needs to output.
So each entry in the font table contains 8 sets 5 instructions that 'output a byte' and then 'find the next byte' ... specifically :-
Load litteral byte to W, Output W byte to PORT (2)
Load W word from Index++, Add W word to PCL (3)
To 'control' this sequence you load 80 Index registers with the offsets that takes the execution from one font location to the next. The last Index register contains an offset that takes you to the Line Sync code, during which time you have to load the Index registers for the next line to be output i.e. mod the contents of the 80 Index registers with 80 new offsets.
Within one line of characters, 'mod the Index registers' means re-setting the FIRST so it 'points' at the next 'font line table start' (i.e. next scan 'line down in the font) - all the other jumps are 'relative' so stay the same.
If you are displaying 8x8 character fonts you will typically have a 2 raster scanline inter-character-line 'gap' (so 480 scan lines will show 48 lines of 80 character text) and 2 lines is plenty of time to work out the 'relative offsets' for the next set of 80 of characetrs.
Note this is for single color B&W text == it can't do even B&W 'wire-frame' bit-mapped graphics without the help of external RAM (8kb is not enough space to store all the bit-map data, you need at least 640*480/8 = 38,400 bytes)