WordPress wpautop/shortcode generating invalid markup

Try this:

// replaces [banner ...] .. [/banner] with <!--banner ID--> before the_content filters run
add_filter('the_content', 'protect_my_shortcode', -100);
function protect_my_shortcode($content){
  return preg_replace_callback('/\[(banner)\b(.*?)(?:(\/))?\](.+?)\[\/\1\]/s', 'protect_my_shortcode_callback', $content);
}

function protect_my_shortcode_callback($matches){
  global $__banners;
  $id = '<!--banner '.count($__banners).'-->';

  $__banners[$id] = do_shortcode($matches[0]);
  // or '['.$matches[1].' '.$matches[2].']'.$matches[4].'[/'.$matches[1].']'
  // if you need to run any filters on the shortcode content ($matches[4])

  return $id;
}

// replaces <!--banner ID--> with the processed shortcode after all the filters run
add_filter('the_content', 'unprotect_my_shortcode', 100);
function unprotect_my_shortcode($content){
  global $__banners;
  return str_replace(array_keys($__banners), array_values($__banners), $content);
}